package cz.cuni.mff.d3s.alloy.core

import edu.mit.csail.sdg.alloy4compiler.ast.Sig.Field
import scala.collection.JavaConversions._
import edu.mit.csail.sdg.alloy4compiler.translator._
import collection.mutable.{HashMap, HashSet, Set}

/**
 * @author Jaroslav Keznikl 
 */

class InstanceSerializerImpl(private val instance : A4Solution)  extends InstanceSerializer {
	// TODO this dependes on the path format (the file name must have the .als extension)
	private val instanceModule = instance.getOriginalFilename.drop(
			instance.getOriginalFilename.lastIndexOf(java.io.File.separatorChar) + 1).dropRight(4)


	class SigRecord(var name: String, var parentName: String)  {

		def equals(o: SigRecord) = {
			name.equals(o.name)
		}
	}

	/**
	 * Returns the code enforcing the given instance
	 */
	def serializeInstance(): String = {
		val fieldRelations = new StringBuffer
		val oldSigFacts = new StringBuffer
		val sigs = new StringBuffer

		val newSigs = new HashSet[SigRecord]()
		val noInstances = new HashSet[String]()
		val sig2instance = new HashMap[String, Set[String]]()

		for (val sig <- instance.getAllReachableSigs) {
			val sts = instance.eval(sig) // get all tuple sets for the given sig
			val instances = new HashSet[String]() // stores all instances of the given sig

			// true if the current sig should be processed by the printer
			val isRelevant = !sig.builtin && (sig.isAbstract == null)

			val normalizedLabel = normalizeSigLabel(sig.label)

			// for relevant sigs the new instances are recorded
			if (isRelevant) {
				for (t: A4Tuple <- sts) {
					for (val i <- 0 to t.arity-1) {
						val normalizedName = normalizeInstanceLabel(t.atom(i))
						newSigs.add(new SigRecord(normalizedName, normalizedLabel))
						instances.add(normalizedName)
					}
				}
			}
			/*
			 * TODO Generate fact that will prohibit new relations to be created.
			 * TODO Evaluate every field only once.
			 */
			// serialize field relations of the given sig
			for (val field <- sig.getFields) {
				fieldRelations.append(serializeField(field))
			}

			if (instances.size > 0) {
				sig2instance.put(normalizedLabel, instances)
			} else if (isRelevant) {
				noInstances.add(normalizedLabel)
			}
		}


		/* Declare the found instances as new 'one' sigs. */
		for (val r <- newSigs)
			sigs.append(String.format("one sig %s extends %s {}\n", r.name, r.parentName))

		/*
		 * Generate facts that will prohibit the original sigs to be
		 * instantiated (except the already defined instances).
		 */
		for (val parentSig <- sig2instance.keySet) {
			val instances = sig2instance(parentSig)
			oldSigFacts.append(String.format("fact { no (%s - (%s)) }\n", parentSig, instances.mkString(" + ")))
		}
		for (val sig <- noInstances)
			oldSigFacts.append(String.format("fact { no %s }\n", sig))


		return "open " + instanceModule + "\n" +
			sigs + "\n" +
			fieldRelations + "\n" +
			oldSigFacts + "\n" +
			instance.getOriginalCommand
	}

	/**
	 * Serialize the given field
	 */
	def serializeField(f: Field): String = {
		var domains = List[String]()
		val ts = instance.eval(f)

		/* Iterate all tuples for the given field. */
		for (val tuple: A4Tuple <- ts) {
			var relations = List[String]()
			for (val i <- (0 to tuple.arity-1))
				relations = normalizeInstanceLabel(tuple.atom(i)) :: relations

			domains = relations.reverse.mkString(" -> ") :: domains
		}

		if (domains.isEmpty)
			return String.format("fact { no %s.%s }\n", normalizeSigLabel(f.sig.label), f.label);

		return String.format("fact { %s = %s }\n", f.label, domains.mkString(" + "));
	}

	/**
	 * Remove all $ from sig names.
	 * @param sigLabel
	 * @return
	 */
	private def normalizeInstanceLabel(instanceLabel: String) =
		instanceLabel.replaceAll("\\$", "").replaceAll("/", "_")

	private def normalizeSigLabel(sigLabel: String) =
		sigLabel.replaceFirst("^this", instanceModule)
}